package com.tentact.coolmeetingtest.support;

import com.tentact.coolmeetingtest.annotation.AutoBuild;
import com.tentact.coolmeetingtest.util.StringUtil;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.*;

/**
 * @author 星环
 * @date 2023/1/10 16:00:12
 * @description 自动从ResultSet里取字段装进Vo里
 */
public class VoAutoBuilder<T> {
    //测试mybatis的自动装配
    //先取得所有带AutoBuild的类
    //再把他们的公有Setter取出，截取属性名
    //把所有大写字母变成_小写，就能得到resultset的名字
    //存在map里
    private static final Map<Class<?>,Map<String,FieldInfo>> classFieldInfoMap = new HashMap<>();

    static class FieldInfo{
        private Class<?> fieldType;
        private Method method;

    }

    public VoAutoBuilder(){

    }

    /**
     * 传入结果集和要组装对象的类，还有要组装的列名，组装对象
     * @param r 结果集
     * @param tClass 要组装的实体类
     * @param fieldNames 字段名
     * @return 组装好的对象，
     * @throws SQLException
     */
    public T buildOne(ResultSet r, Class<T> tClass,String... fieldNames) throws SQLException {
        if(tClass.getAnnotation(AutoBuild.class) == null){
            return null;
        }
        //实体类->{resultSet字段名、字段类型、set方法
//        Map<Class<?>,Map<String,Method>>
        if(!r.next()){
            return null;
        }
        T t = null;
        try {
            Constructor<T> constructor = tClass.getConstructor(null);
            t = constructor.newInstance();
        } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        Map<String,FieldInfo> fieldInfoMap = getFieldInfos(tClass);
        //把每个对象都取一遍
        try {
            //不指定哪一行，就全取
            if(fieldNames.length == 0){
                fieldNames = fieldInfoMap.keySet().toArray(new String[0]);
            }
            for (String fieldName : fieldNames) {
                FieldInfo fieldInfo = fieldInfoMap.get(fieldName);
                Object data = null;
                try {
                    if(String.class.equals(fieldInfo.fieldType)){
                        data = r.getString(fieldName);
                    }
                    if(Integer.class.equals(fieldInfo.fieldType)){
                        data = r.getInt(fieldName);
                    }
                    if(Timestamp.class.equals(fieldInfo.fieldType)){
                        data = r.getTimestamp(fieldName);
                    }
                    fieldInfo.method.invoke(t,data);
                }catch (SQLException ignored){
                }

            }
        } catch (IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
        return t;
    }

    public List<T> buildAll(ResultSet r, Class<T> tClass,String... fieldNames) throws SQLException {
        List<T> list = new ArrayList<>();
        while (true) {
            T t = buildOne(r, tClass, fieldNames);
            if (t == null) {
                return list;
            }
            list.add(t);
        }
    }

    private Map<String,FieldInfo> getFieldInfos(Class<T> tClass) {

        Map<String,FieldInfo> infoList = classFieldInfoMap.get(tClass);
        if(infoList != null){
            return infoList;
        }
        //不存在，加入
        Method[] methods = tClass.getMethods();
        Map<String,FieldInfo> fieldInfoMap = new HashMap<>();
        //遍历每个共有方法
        for (Method method : methods) {
            //是set方法，准备获取一个fieldInfo
            if(method.getName().substring(0,3).equals("set")){
                FieldInfo info = new FieldInfo();
                String beanName = method.getName().substring(3);
                String sql_name = StringUtil.beanToSQLName(beanName);
                Class<?>[] parameterTypes = method.getParameterTypes();
                if(parameterTypes.length == 1){
                    if(parameterTypes[0].getName().equals("int")){
                        info.fieldType = Integer.class;
                    }else {
                        info.fieldType = parameterTypes[0];
                    }
                }
                info.method = method;
                fieldInfoMap.put(sql_name,info);
            }
        }
        classFieldInfoMap.put(tClass,fieldInfoMap);
        return fieldInfoMap;
    }
}
